/**
 * DVN: Distributed Virtual Network Simulation Platform
 * Copyright (c) 2006-2009 Tyson Malchow <tyson.malchow@gmail.com>
 *
 * This file is part of the DVN distributed virtual network simulation
 * program.
 *
 * DVN is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * DVN is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with DVN.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <setjmp.h>

#include "snricall.h"
#include "snricall_codes.h"
#include "context.h"
#include "sim.h"
#include "module.h"
#include "vci.h"
#include "timer.h"

static int snricall_getip(va_list va) {
	va_list vac;
	int rv = 1;
	int * ip_cnt;
	in_addr_t ** ip_arr;
	static in_addr_t ip_list[1];

	va_copy(vac, va);
	ip_cnt = va_arg(vac, int*);
	ip_arr = va_arg(vac, in_addr_t**);

	if(ip_cnt && ip_arr) {
		*ip_cnt = 1;
		*ip_arr = ip_list;
		ip_list[0] = global_sim_context.current_context->address;
	} else
		rv = 0;

	va_end(vac);
	return rv;
}

static int snricall_close(va_list va) {
	int socketid;
	va_list vac;

	va_copy(vac, va);
	socketid = va_arg(vac, int);
	va_end(vac);

	vci_close_socket(global_sim_context.sim_worker->vci_mgr, global_sim_context.current_context->address, socketid);

	return 1;
}

/**
 * creates a timer
 * unsigned int	- number of milliseconds from now the timer should expire
 * void (*t)(int timerid) - timer callback - function called when timer expires
 * void *		- timer callback argument
 *
 * int* 		- output/timer identifier
 */
static int snricall_create_timer(va_list va) {
	int rv = 1;
	int *timer_id_p, timer_id;
	unsigned int delay;
	void * cb_arg;
	dtimer_ontimer_cb_fp cb;

	va_list vac;
	va_copy(vac, va);
	delay = va_arg(vac, unsigned int);
	cb = va_arg(vac,dtimer_ontimer_cb_fp);
	cb_arg = va_arg(vac,void*);
	timer_id_p = va_arg(vac, int*);
	va_end(vac);

	timer_id = dtimer_create_timer(global_sim_context.sim_worker->timer_mgr, global_sim_context.sim_worker->current_time, global_sim_context.current_context, delay, cb, cb_arg);
	if(timer_id == TIMER_INVALID)
		rv = 0;
	else if(timer_id_p)
		*timer_id_p = timer_id;

	return rv;
}

static int snricall_destroy_timer(va_list va) {
	int rv = 1;
	int timer_id;

	va_list vac;
	va_copy(vac, va);
	timer_id = va_arg(vac, int);
	va_end(vac);

	dtimer_destroy_timer(global_sim_context.sim_worker->timer_mgr, global_sim_context.current_context, timer_id);

	return rv;
}

void snricall_exit(va_list va) {
	if(global_sim_context.exit_usable) {
		sim_worker_destroy_node(global_sim_context.sim_worker, global_sim_context.current_context);
		global_sim_context.current_context = NULL;
		longjmp(global_sim_context.exit_env,1);
	} else
		dlogf(LOG_ERR, "Module made SNRI exit call when invalid to do so. Ignoring.\n");
	return;
}

static int snricall_gettime(va_list va) {
	int rv = 1;
	struct timeval * tv;
	va_list vac;

	va_copy(vac, va);
	tv = va_arg(vac, struct timeval*);
	va_end(vac);

	if(tv) {
		tv->tv_sec = global_sim_context.sim_worker->current_time / 1000;
		tv->tv_usec = (global_sim_context.sim_worker->current_time % 1000) * 1000;
	} else
		rv = 0;

	return rv;
}


/**
 * sends some network data
 * int			- socket identifier to send from
 * inet_addr_t  - destination address
 * int          - destination port
 * unsigned int - data length
 * char * 		- data to send
 */
static int snricall_datagram_send(va_list va) {
	int rv;
	int dest_port, source_socket;
	in_addr_t dest_addr;
	unsigned int data_length;
	char * data;
	va_list vac;
	va_copy(vac, va);

	source_socket=va_arg(vac, int);
	dest_addr=va_arg(vac,in_addr_t);
	dest_port=va_arg(vac,int);
	data_length=va_arg(vac,unsigned int);
	data=va_arg(vac,char*);
	va_end(vac);

	rv = vci_send(global_sim_context.sim_worker->vci_mgr, global_sim_context.sim_worker->current_time,
				global_sim_context.current_context->address, source_socket,
				dest_addr, dest_port,
				data, data_length);

	return rv;
}

/**
 * allocates a network datagram socket
 * inet_addr_t 	- bind address - INADDR_ANY for all ips
 * int 			- port_number
 * vci_onrecv_cb_tp - callback for new datagrams
 *
 * int*         - output/socket identifier
 */
static int snricall_datagram_socket(va_list va) {
	int rv = 1;
	in_addr_t bind_addr;
	vci_onrecv_cb_tp socket_callback;
	int port;
	int socket_id;
	int *out_socket;
	va_list vac;

	va_copy(vac, va);
	bind_addr = va_arg(vac, in_addr_t);
	port = va_arg(vac, int);
	socket_callback = va_arg(vac, vci_onrecv_cb_tp);
	out_socket = va_arg(vac, int*);
	va_end(vac);

	if(out_socket &&
			(bind_addr == global_sim_context.current_context->address || bind_addr == INADDR_LOOPBACK || bind_addr == INADDR_ANY) &&
			(socket_id = vci_create_datagram_socket(global_sim_context.sim_worker->vci_mgr, global_sim_context.current_context->address, port, socket_callback)) != -1)
		*out_socket = socket_id;
	else
		rv = 0;

	return rv;
}

static int snricall_log(va_list va) {
	int rv = 1, log_level;
	char * data;
	unsigned int data_length;
	va_list vac;

	va_copy(vac, va);
	log_level = va_arg(vac, int);
	data = va_arg(vac, char *);
	data_length = va_arg(vac, unsigned int);
	va_end(vac);

	if(global_sim_context.current_context == NULL)
		dlog_channel_write(0,  data, data_length);

	else if(global_sim_context.current_context->log_level >= log_level)
		dlog_channel_write(global_sim_context.current_context->log_channel, data, data_length);

	return rv;
}

static int snricall_register_globals(va_list va) {
	va_list vac;
	int rv = 0;

	va_copy(vac, va);
	if(global_sim_context.static_context) {
		module_register_globals(global_sim_context.static_context, vac);
		rv = 1;
	}
	va_end(vac);

	return rv;
}

int snricall(int call_code, ...) {
	va_list va;
	int rv = 0;

	va_start(va, call_code);
	switch(call_code) {
		case SNRICALL_CLOSE:
			rv = snricall_close(va);
			break;
		case SNRICALL_CREATE_TIMER:
			rv = snricall_create_timer(va);
			break;
		case SNRICALL_DESTROY_TIMER:
			rv = snricall_destroy_timer(va);
			break;
		case SNRICALL_EXIT:
			snricall_exit(va); /* will never return */
			break;
		case SNRICALL_GETIP:
			rv = snricall_getip(va);
			break;
		case SNRICALL_GETTIME:
			rv = snricall_gettime(va);
			break;
		case SNRICALL_LOG:
			rv = snricall_log(va);
			break;
		case SNRICALL_DATAGRAM_SEND:
			rv = snricall_datagram_send(va);
			break;
		case SNRICALL_DATAGRAM_SOCKET:
			rv = snricall_datagram_socket(va);
			break;
		case SNRICALL_REGISTER_GLOBALS:
			rv = snricall_register_globals(va);
			break;
	}
	va_end(va);

	return rv;
}

